#include <iostream>
#include <sstream>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
#include "ruby-m3g-internal.hpp"
using namespace m3g;
using namespace std;

namespace {
    struct BoneTransformAccessor {
        SkinnedMesh* skinned_mesh;
    };
    VALUE rb_cSkinnedMesh_BoneTransformAccessor;
}

void ruby_SkinnedMesh_mark (SkinnedMesh* ptr)
{
    vector<Object3D*> objs;
    extract_all_references (ptr, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        VALUE val_obj = (VALUE)objs[i]->getExportedEntity();
        if (val_obj)
            rb_gc_mark (val_obj);
    }
}

void ruby_SkinnedMesh_free (SkinnedMesh* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~SkinnedMesh ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_SkinnedMesh_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(SkinnedMesh));
    return Data_Wrap_Struct (self, ruby_SkinnedMesh_mark, ruby_SkinnedMesh_free, p);
}

VALUE ruby_SkinnedMesh_initialize (VALUE self, VALUE val_vertices, VALUE val_submeshes, VALUE val_appearances, VALUE val_skeleton)
{
    if (!rb_obj_is_kind_of (val_vertices, rb_cVertexBuffer)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::VertexBuffer)" );
    }
    if (!((TYPE(val_submeshes)==T_ARRAY && TYPE(val_appearances)==T_ARRAY) ||
          (TYPE(val_submeshes)==T_DATA  && TYPE(val_appearances)==T_DATA))) {
        rb_raise (rb_eArgError, "wrong argument type (expected IndexBuffer or Array of IndexBuffer and Appearance or Array of Appearance)");
    }
    if (!rb_obj_is_kind_of (val_skeleton, rb_cNode)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::Node)" );
    }
    SkinnedMesh*  p;
    Data_Get_Struct (self, SkinnedMesh, p);
    VertexBuffer* vertices;
    Data_Get_Struct (val_vertices, VertexBuffer, vertices);
    Group*        skeleton;
    Data_Get_Struct (val_skeleton, Group, skeleton);

    switch (TYPE(val_submeshes)) {
    case T_ARRAY: {
        int           num_submeshes   = RARRAY_LEN(val_submeshes);
        int           num_appearances = RARRAY_LEN(val_appearances);
        IndexBuffer** submeshes   = (IndexBuffer**)ruby_xmalloc (sizeof(IndexBuffer*)*num_submeshes);
        Appearance**  appearances = (Appearance**) ruby_xmalloc (sizeof(Appearance* )*num_appearances);
        for (int i = 0; i < num_submeshes; i++) {
            VALUE val_submesh = rb_ary_entry(val_submeshes, i);
            if (!rb_obj_is_kind_of (val_submesh, rb_cIndexBuffer)) {
                rb_raise (rb_eArgError, "wrong argument type (expected M3G::IndexBuffer)" );
            }
            Data_Get_Struct (val_submesh, IndexBuffer, submeshes[i]);
        }
        for (int i = 0; i < num_appearances; i++) {
            VALUE val_appearance = rb_ary_entry(val_appearances, i);
            if (!rb_obj_is_kind_of (val_appearance, rb_cAppearance)) {
                rb_raise (rb_eArgError, "wrong argument type (expected M3G::Appearance)" );
            }
            Data_Get_Struct (val_appearance, Appearance, appearances[i]);
        }
        __TRY__;
        new (p) SkinnedMesh (vertices, num_submeshes, submeshes, appearances, skeleton);
        __CATCH__;
        ruby_xfree (submeshes);
        ruby_xfree (appearances);
        break;
    }
    case T_DATA: {
        if (!rb_obj_is_kind_of (val_submeshes, rb_cIndexBuffer)) {
            rb_raise (rb_eArgError, "wrong argument type (expected M3G::IndexBuffer)" );
        }
        if (!rb_obj_is_kind_of (val_appearances, rb_cAppearance)) {
            rb_raise (rb_eArgError, "wrong argument type (expected M3G::Appearance)" );
        }
        IndexBuffer* submesh;
        Appearance*  appearance;
        Data_Get_Struct (val_submeshes, IndexBuffer, submesh);
        Data_Get_Struct (val_appearances, Appearance, appearance);
        __TRY__;
        new (p) SkinnedMesh (vertices, submesh, appearance, skeleton);
        __CATCH__;
        break;
    }
    default: {
        rb_raise (rb_eArgError, "wrong argument type (expected IndexBuffer and Appearance (or Array))");
    }
    }

    p->setExportedEntity ((void*)self);
    return self;
}

VALUE ruby_SkinnedMesh_get_bone_transform (int argc, VALUE* argv, VALUE self)
{
    SkinnedMesh* p;
    Data_Get_Struct (self, SkinnedMesh, p);
    VALUE val_ret;

    VALUE val_arg1;
    int num = rb_scan_args (argc, argv, "01", &val_arg1);

    switch (num) {
    case 0: {
        BoneTransformAccessor* accessor;
        VALUE              val_accessor = Data_Make_Struct (rb_cSkinnedMesh_BoneTransformAccessor, BoneTransformAccessor, 0, -1, accessor);
        accessor->skinned_mesh = p;
        val_ret = val_accessor;
        break;
    }
    case 1: {
        VALUE val_bone = val_arg1;
        if (!rb_obj_is_kind_of (val_bone, rb_cNode)) {
            rb_raise (rb_eArgError, "wrong argument type (expected M3G::Node)" );
        }
        Node* bone;
        Data_Get_Struct (val_bone, Node, bone);
        Transform*   trans;
        VALUE    val_trans = Data_Make_Struct (rb_cTransform, Transform, ruby_Transform_mark, ruby_Transform_free, trans);
        ruby_Transform_initialize (0, NULL, val_trans);
        __TRY__;
        p->getBoneTransform (bone, trans);
        __CATCH__;
        val_ret = val_trans;
        break;
    }
    default: {
        rb_raise (rb_eArgError, "wrong number of arguments (%d for 0 or 1)", num);
    }
    }
    
    return val_ret;
}

VALUE ruby_SkinnedMesh_get_bone_vertices (VALUE self, VALUE val_bone)
{
    if (!rb_obj_is_kind_of (val_bone, rb_cNode)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::Node)" );
    }
    SkinnedMesh* p;
    Data_Get_Struct (self, SkinnedMesh, p);
    Node* bone;
    Data_Get_Struct (val_bone, Node, bone);
    int   count;
    __TRY__;
    count = p->getBoneVertices (bone, NULL, NULL);
    __CATCH__;
    int*   indices = (int*)ruby_xmalloc (sizeof(int)*count);
    float* weights = (float*)ruby_xmalloc (sizeof(float)*count);
    __TRY__;
    p->getBoneVertices (bone, indices, weights);
    __CATCH__;
    VALUE val_indices = rb_ary_new2 (count);
    VALUE val_weights = rb_ary_new2 (count);
    for (int i = 0; i < count; i++) {
        rb_ary_push (val_indices, rb_float_new(indices[i]));
        rb_ary_push (val_weights, rb_float_new(weights[i]));
    }
    ruby_xfree (indices);
    ruby_xfree (weights);

    VALUE val_ret = rb_ary_new2 (3);
    rb_ary_push (val_ret, INT2NUM(count));
    rb_ary_push (val_ret, val_indices);
    rb_ary_push (val_ret, val_weights);
    return val_ret;
}

VALUE ruby_SkinnedMesh_get_skeleton (VALUE self)
{
    SkinnedMesh* p;
    Data_Get_Struct (self, SkinnedMesh, p);
    Group* skeleton;
    __TRY__;
    skeleton = p->getSkeleton ();
    __CATCH__;
    return skeleton ? (VALUE)skeleton->getExportedEntity() : Qnil;
}

VALUE ruby_SkinnedMesh_print (VALUE self)
{
    SkinnedMesh* p;
    Data_Get_Struct (self, SkinnedMesh, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}

/**
 *  SkinnedMesh:: BoneTransformAccessor
 */
VALUE ruby_SkinnedMesh_BoneTransformAccessor_add_transform (VALUE self, VALUE val_args)
{
    rb_check_type (val_args, T_ARRAY);
    if (RARRAY_LEN(val_args) != 4) {
        rb_raise (rb_eArgError, "wrong array length %d (expected %d)", RARRAY_LEN(val_args));
    }
    VALUE val_bone         = rb_ary_entry(val_args, 0);
    VALUE val_weight       = rb_ary_entry(val_args, 1);
    VALUE val_first_vertex = rb_ary_entry(val_args, 2);
    VALUE val_num_vertices = rb_ary_entry(val_args, 3);
    if (!rb_obj_is_kind_of (val_bone, rb_cNode)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::Node)" );
    }

    BoneTransformAccessor* p;
    Data_Get_Struct (self, BoneTransformAccessor, p);
    Node* bone;
    Data_Get_Struct (val_bone, Node, bone);
    int weight       = NUM2INT(val_weight);
    int first_vertex = NUM2INT(val_first_vertex);
    int num_vertices = NUM2INT(val_num_vertices);
    __TRY__;
    p->skinned_mesh->addTransform (bone, weight, first_vertex, num_vertices);
    __CATCH__;
    return Qnil;
}


/**
 *
 */
void register_SkinnedMesh ()
{
    // SkinnedMesh
    rb_cSkinnedMesh         = rb_define_class_under (rb_mM3G, "SkinnedMesh",         rb_cMesh);

    rb_define_alloc_func     (rb_cSkinnedMesh, ruby_SkinnedMesh_allocate);
    rb_define_private_method (rb_cSkinnedMesh, "initialize", (VALUE(*)(...))ruby_SkinnedMesh_initialize, 4);

    //rb_define_method (rb_cSkinnedMesh, "add_transform",   (VALUE(*)(...))ruby_SkinnedMesh_add_transform,      4);
    //rb_define_method (rb_cSkinnedMesh, "bone_transform",  (VALUE(*)(...))ruby_SkinnedMesh_get_bone_transform, 1);
    rb_define_method (rb_cSkinnedMesh, "bone_transform",  (VALUE(*)(...))ruby_SkinnedMesh_get_bone_transform, -1);
    rb_define_method (rb_cSkinnedMesh, "bone_vertices",   (VALUE(*)(...))ruby_SkinnedMesh_get_bone_vertices,  1);
    rb_define_method (rb_cSkinnedMesh, "skeleton",        (VALUE(*)(...))ruby_SkinnedMesh_get_skeleton,       0);
    rb_define_method (rb_cSkinnedMesh, "to_s"         ,   (VALUE(*)(...))ruby_SkinnedMesh_print       ,       0);

    // SkinnedMesh:: BoneTransform
    rb_cSkinnedMesh_BoneTransformAccessor    = rb_define_class_under (rb_cSkinnedMesh, "BoneTransformAccessor",    rb_cObject);

    rb_define_method (rb_cSkinnedMesh_BoneTransformAccessor, "<<",       (VALUE(*)(...))ruby_SkinnedMesh_BoneTransformAccessor_add_transform,   1);

}

